package gcloud_k8s

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/remotecommand"
	logger "platon-tools/go-service/go-logger"
	"time"
)

type PodEle struct {
	Name       string     `json:"name"`
	IP         string     `json:"ip"`
	Status     string     `json:"status"`
	StartTime  time.Time  `json:"start_time"`
	CreateTime time.Time  `json:"create_time"`
	Label      PodLabel   `json:"label"`
	SvcIP      string     `json:"svc_ip"`
	SvcName    string     `json:"svc_name"`
	Metrics    PodMetrics `json:"metrics"`
	NodeID     string     `json:"node_id"`
}

type PodLabel struct {
	Name string `json:"name"`
	Type string `json:"type"`
	Hash string `json:"hash"`
}

func (gc *GCPlatonK8s) WatchPods(ctx context.Context) error {
	podWatch, err := gc.k8sClientset.CoreV1().Pods("").Watch(gc.ctx, metav1.ListOptions{})
	if err != nil {
		logger.Logger.Error("watch pods", zap.Error(err))
		return err
	}

	for {
		select {
		case <-ctx.Done():
			return nil
		case <-gc.ctx.Done():
			return nil
		case e, ok := <-podWatch.ResultChan():
			if ok {
				logger.Logger.Info("watch chan is ok", zap.String("type", string(e.Type)))
			} else {
				return nil
			}
		}
	}

}

func (gc *GCPlatonK8s) WatchPodsByName(name string) error {
	podWatch, err := gc.k8sClientset.CoreV1().Pods("").Watch(gc.ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("app=%s", name)})
	if err != nil {
		logger.Logger.Error("watch pods", zap.Error(err))
		return err
	}

	for {
		select {
		case e, ok := <-podWatch.ResultChan():
			if ok {
				logger.Logger.Info("watch chan is ok", zap.String("type", string(e.Type)))
			} else {
				return nil
			}
		}
	}

	return nil
}

func (gc *GCPlatonK8s) GetPods() []PodEle {
	//gc.rw.RLocker()
	//defer gc.rw.RUnlock()
	return gc.podList
}

func (gc *GCPlatonK8s) GetPodByName(name string) *PodEle {
	gc.rw.RLocker()
	defer gc.rw.RUnlock()
	for _, p := range gc.podList {
		if p.Label.Name == name {
			var pod = p
			return &pod
		}
	}

	return nil
}

func (gc *GCPlatonK8s) GetPodFromNameByOnline(name string) *PodEle {
	pods := gc.GetPodsFromRest()
	for _, p := range pods {
		if p.Label.Name == name {
			pod := p
			return &pod
		}
	}

	return nil
}

func (gc *GCPlatonK8s) GetPodsFromRest() []PodEle {

	podsRequest := &corev1.PodList{}
	if err := gc.restClient.
		Get().
		Namespace("default").
		Resource("pods").
		VersionedParams(&metav1.ListOptions{Limit: 500}, scheme.ParameterCodec).
		Do(gc.ctx).
		Into(podsRequest); err != nil {
		logger.Logger.Error("get pod list", zap.Error(err))
		return nil
	}

	svcs := &corev1.ServiceList{}
	if err := gc.restClient.
		Get().
		Namespace("default").
		Resource("Services").
		VersionedParams(&metav1.ListOptions{Limit: 500}, scheme.ParameterCodec).
		Do(gc.ctx).
		Into(svcs); err != nil {
		logger.Logger.Error("get service list", zap.Error(err))
	}

	podsMetrics, err := gc.GetPodsMetrics()
	if err != nil {
		return nil
	}

	pods := make([]PodEle, 0)

	for _, p := range podsRequest.Items {
		if p.Status.Phase == corev1.PodRunning {
			logger.Logger.Sugar().Debugf("%s state: 【%v】", p.Name, p.Status)
			appName := p.Labels["app"]
			pod := PodEle{
				Name:       p.Name,
				IP:         p.Status.PodIP,
				Status:     string(p.Status.Phase),
				StartTime:  p.Status.StartTime.Time,
				CreateTime: p.CreationTimestamp.Time,
				Label: PodLabel{
					Name: p.Labels["app"],
					Type: p.Labels["type"],
					Hash: p.Labels["pod-template-hash"],
				},
			}
			for _, svc := range svcs.Items {
				if svc.Spec.Selector["app"] == appName {
					loadBalancerIngress := svc.Status.LoadBalancer.Ingress
					pod.SvcIP = loadBalancerIngress[0].IP
					pod.SvcName = svc.Name
				}
			}

			for _, metrics := range podsMetrics.Items {
				if metrics.Metadata.Name == p.Name {
					pod.Metrics = metrics
				}
			}

			if id, err := gc.GetNodeIdFromPod(p.Name); err == nil {
				pod.NodeID = id
			}

			pods = append(pods, pod)
		}

	}

	return pods
}

func (gc *GCPlatonK8s) GetPodsMetrics() (*PodMetricsList, error) {

	if data, err := gc.restClient.Get().AbsPath("/apis/metrics.k8s.io/v1beta1/namespaces/default/pods").DoRaw(gc.ctx); err != nil {
		logger.Logger.Error("get pods metrics list", zap.Error(err))
		return nil, err
	} else {
		var metricsList PodMetricsList
		if err := json.Unmarshal(data, &metricsList); err != nil {
			logger.Logger.Error("unmarshal pods metrics failed", zap.Error(err))
			return nil, err
		}

		return &metricsList, nil
	}
}

func (gc *GCPlatonK8s) GetNodeIdFromPod(name string) (string, error) {
	// platon attach http://127.0.0.1:6789 --exec "admin.nodeInfo.id"
	req := gc.restClient.Post().
		Resource("pods").
		Name(name).
		Namespace("default").
		SubResource("exec").
		VersionedParams(&corev1.PodExecOptions{
			Command: []string{"cat", "/root/platon-node/data/nodeid"},
			Stdin:   true,
			Stdout:  true,
			Stderr:  true,
			TTY:     false,
		}, scheme.ParameterCodec)

	// remotecommand 主要实现了http 转 SPDY 添加X-Stream-Protocol-Version相关header 并发送请求
	exec, err := remotecommand.NewSPDYExecutor(gc.config, "POST", req.URL())

	var (
		out    bytes.Buffer
		errOut bytes.Buffer
		in     bytes.Buffer
	)

	// 建立链接之后从请求的sream中发送、读取数据
	if err = exec.Stream(remotecommand.StreamOptions{
		Stdin:  &in,
		Stdout: &out,
		Stderr: &errOut,
		Tty:    false,
	}); err != nil {
		logger.Logger.Sugar().Debug("Stream failed", err)
		return "", err
	}

	id := out.Bytes()
	return string(id[:len(id)-1]), nil
}

type PodMetricsList struct {
	Kind       string `json:"kind"`
	APIVersion string `json:"apiVersion"`
	Metadata   struct {
		SelfLink string `json:"selfLink"`
	} `json:"metadata"`
	Items []PodMetrics `json:"items"`
}

type PodMetrics struct {
	Metadata struct {
		Name              string    `json:"name"`
		Namespace         string    `json:"namespace"`
		SelfLink          string    `json:"selfLink"`
		CreationTimestamp time.Time `json:"creationTimestamp"`
	} `json:"metadata"`
	Timestamp  time.Time `json:"timestamp"`
	Window     string    `json:"window"`
	Containers []struct {
		Name  string `json:"name"`
		Usage struct {
			CPU    string `json:"cpu"`
			Memory string `json:"memory"`
		} `json:"usage"`
	} `json:"containers"`
}
